import {createContext, useContext} from "react";
import {PIANO_BLACK_KEY_NUM, PIANO_WHITE_KEY_NUM} from "../../services/Piano.ts";

// 该数组内的信息仅用于渲染每个琴键的位置
// 我们将任意一个音符与它高八度的音符成为一组，如 Do 记作 C，则将 C1-C8 记为一组音符
// 注意到一组音符内的任意两个琴键都差七个琴键的宽度的倍数
// 因此只要知道第一个这一组内第一个琴键的位置即可知道该组其他琴键的位置
// 第一个白键的位置为 base*白键的宽度
// 第一个黑键的位置为 base*白键的宽度 （没写错，黑键的位置也是通过白键计算）
const keyNoteGroups: { type: 'white' | 'black', notes: string[], base: number }[] = [
    {
        type: 'white',
        notes: ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7"],
        base: 0
    },
    {
        type: 'white',
        notes: ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7"],
        base: 1
    },
    {
        type: 'white',
        notes: ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8"],
        base: 2
    },
    {
        type: 'white',
        notes: ["D1", "D2", "D3", "D4", "D5", "D6", "D7"],
        base: 3
    },
    {
        type: 'white',
        notes: ["E1", "E2", "E3", "E4", "E5", "E6", "E7"],
        base: 4
    },
    {
        type: 'white',
        notes: ["F1", "F2", "F3", "F4", "F5", "F6", "F7"],
        base: 5
    },
    {
        type: 'white',
        notes: ["G1", "G2", "G3", "G4", "G5", "G6", "G7"],
        base: 6
    },
    {
        type: 'black',
        notes: ["A#0", "A#1", "A#2", "A#3", "A#4", "A#5", "A#6", "A#7"],
        base: 0.7
    },
    {
        type: 'black',
        notes: ["C#1", "C#2", "C#3", "C#4", "C#5", "C#6", "C#7"],
        base: 2.7
    },
    {
        type: 'black',
        notes: ["D#1", "D#2", "D#3", "D#4", "D#5", "D#6", "D#7"],
        base: 3.7
    },
    {
        type: 'black',
        notes: ["F#1", "F#2", "F#3", "F#4", "F#5", "F#6", "F#7"],
        base: 5.7
    },
    {
        type: 'black',
        notes: ["G#1", "G#2", "G#3", "G#4", "G#5", "G#6", "G#7"],
        base: 6.7
    }
]

export interface PianoKeyElement {

    type: 'white' | 'black';

    // 琴键左侧 与 原点 (A0 左侧) 的水平距离
    offsetX: number;

    note: string;

    width: number;

    // 琴键在真实的屏幕上渲染的横轴，该坐标以 canvus/屏幕中的最左侧位置为 0
    x?: number;

    attacked?: boolean;
}

export class PianoKeyboardElement {

    public static readonly WHITE_KEY_NUM = PIANO_WHITE_KEY_NUM;

    public static readonly BLACK_KEY_NUM = PIANO_BLACK_KEY_NUM;

    public readonly whiteKeyWidth: number;

    public readonly blackKeyWidth: number;

    private pianoKeyElementMap: Map<string, PianoKeyElement>;

    public constructor(whiteKeyWidth: number, blackKeyWidth: number) {
        this.whiteKeyWidth = whiteKeyWidth;
        this.blackKeyWidth = blackKeyWidth;
        this.pianoKeyElementMap = this.createPianoKeyboard(this.whiteKeyWidth, this.blackKeyWidth);
    }

    public getPianoKey(note: string): PianoKeyElement | undefined {
        return this.pianoKeyElementMap.get(note);
    }

    public getSortedPianoKeys(): PianoKeyElement[] {
        return [...this.pianoKeyElementMap.values()]
            .sort((a, b) => a.offsetX - b.offsetX);
    }

    private createPianoKeyboard(whiteKeyWidth: number, blackKeyWidth: number): Map<string, PianoKeyElement> {

        const keyElements: PianoKeyElement[] = [];

        for (const noteGroup of keyNoteGroups) {
            for (let j = 0; j < noteGroup.notes.length; j++) {
                const note = noteGroup.notes[j];
                keyElements.push({
                    type: noteGroup.type,
                    offsetX: (noteGroup.base + 7 * j) * whiteKeyWidth,
                    note,
                    width: noteGroup.type === 'white' ? whiteKeyWidth : blackKeyWidth
                })
            }
        }

        const map: Map<string, PianoKeyElement> = new Map();

        for (const keyElement of keyElements) {
            map.set(keyElement.note, keyElement);
        }

        return map;
    }
}

export const PianoKeyContext = createContext<
    {
        pianoKeyboard: PianoKeyboardElement | null,
        pianoKeyboardWidth: number,
        pianoKeyboardHeight: number,
        topAreaHeight: number
    }
>({
    pianoKeyboard: null,
    pianoKeyboardWidth: 0,
    pianoKeyboardHeight: 0,
    topAreaHeight: 0
});

export const usePianoKeyboard = () => useContext(PianoKeyContext);